{{!

  Copyright (c) Facebook, Inc. and its affiliates.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

}}

    use fbthrift::*;
    {{#program:anyServiceWithoutParent?}}
    use std::marker::PhantomData;
    {{/program:anyServiceWithoutParent?}}
    use std::sync::Arc;{{!
}}{{#program:services}}

    pub struct {{service:name}}Impl<P, T> {
        {{#service:extends}}
        parent: {{service:package}}::client::{{service:name}}Impl<P, T>,
        {{/service:extends}}
        {{^service:extends}}
        transport: T,
        _phantom: PhantomData<fn() -> P>,
        {{/service:extends}}
    }

    impl<P, T> {{service:name}}Impl<P, T> {
        pub fn new(
            transport: T,
        ) -> Self {
            {{#service:extends}}
            let parent = {{service:package}}::client::{{service:name}}Impl::<P, T>::new(transport);
            Self { parent }
            {{/service:extends}}
            {{^service:extends}}
            Self {
                transport,
                _phantom: PhantomData,
            }
            {{/service:extends}}
        }

        pub fn transport(&self) -> &T {
            {{#service:extends}}
            self.parent.transport()
            {{/service:extends}}
            {{^service:extends}}
            &self.transport
            {{/service:extends}}
        }
    }

    {{#service:extendedServices}}
    impl<P, T> {{!
        }}{{#extendedService:service}}{{!
        }}AsRef<dyn {{extendedService:packagePrefix}}::client::{{service:name}} + 'static> {{!
        }}{{/extendedService:service}}{{!
        }}for {{service:name}}Impl<P, T>
    where
        P: Protocol,
        T: Transport,
        {{! require P::Frame and T to have compatible DecBuf and EncBuf::Final }}
        P::Frame: Framing<DecBuf = FramingDecoded<T>>,
        ProtocolEncoded<P>: BufMutExt<Final = FramingEncodedFinal<T>>,
    {
        fn as_ref(&self) -> &(dyn {{#extendedService:service}}{{!
            }}{{extendedService:packagePrefix}}::client::{{service:name}}{{!
            }}{{/extendedService:service}}{{!
            }} + 'static)
        {
            {{extendedService:asRefImpl}}
        }
    }

    {{/service:extendedServices}}
    pub trait {{service:name}}: {{!
        }}{{#service:extends}}{{service:package}}::client::{{service:name}} + {{/service:extends}}{{!
        }}Send {{>lib/block}}{{!

        }}{{#service:functions}}{{^function:any_streams?}}
        fn {{function:name}}(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
        ) -> std::pin::Pin<Box<dyn std::future::Future<Output = std::result::Result<{{!
            }}{{#function:returnType}}{{>lib/type}}{{/function:returnType}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + Send + 'static>>;{{!
        }}{{/function:any_streams?}}{{/service:functions}}
    }

    impl<P, T> {{service:name}} for {{service:name}}Impl<P, T>
    where
        P: Protocol,
        T: Transport,
        {{! require P::Frame and T to have compatible DecBuf and EncBuf::Final }}
        P::Frame: Framing<DecBuf = FramingDecoded<T>>,
        ProtocolEncoded<P>: BufMutExt<Final = FramingEncodedFinal<T>>,
    {{>lib/block}}{{!
        }}{{#service:functions}}{{^function:any_streams?}}
        fn {{function:name}}(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
        ) -> std::pin::Pin<Box<dyn std::future::Future<Output = std::result::Result<{{!
            }}{{#function:returnType}}{{>lib/type}}{{/function:returnType}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + Send + 'static>> {
            use futures::future::{FutureExt, TryFutureExt};
            let request = serialize!(P, |p| protocol::write_message(
                p,
                "{{function:name}}",
                MessageType::Call,
                // Note: we send a 0 message sequence ID from clients because
                // this field should not be used by the server (except for some
                // language implementations).
                0,
                |p| {
                    p.write_struct_begin("args");{{!
                    }}{{#function:args}}
                    p.write_field_begin({{!
                        }}"arg_{{field:name}}", {{!
                        }}{{#field:type}}{{>lib/ttype}}{{/field:type}}, {{!
                        }}{{field:key}}i16{{!
                    }});
                    arg_{{field:name}}.write(p);
                    p.write_field_end();{{!
                    }}{{/function:args}}
                    p.write_field_stop();
                    p.write_struct_end();
                },
            ));
            self.transport()
                .call(request)
                .map_err(From::from)
                .and_then(|reply| futures::future::ready({
                    let de = P::deserializer(reply);
                    move |mut p: P::Deserializer| -> std::result::Result<{{!
                        }}{{#function:returnType}}{{>lib/type}}{{/function:returnType}}, {{!
                        }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
                    }}> {
                        let p = &mut p;
                        let (_, message_type, _) = p.read_message_begin(|_| ())?;
                        let result = match message_type {
                            MessageType::Reply => {
                                let exn = {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::read(p)?;
                                match exn {
                                    {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::Success(x) => Ok(x),
                                    {{#function:exceptions}}
                                    {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::{{field:name}}(err) => {
                                        Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::{{field:name}}(err))
                                    }
                                    {{/function:exceptions}}
                                    {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::ApplicationException(ae) => {
                                        Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ApplicationException(ae))
                                    }
                                }
                            }
                            MessageType::Exception => {
                                let ae = ApplicationException::read(p)?;
                                Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ApplicationException(ae))
                            }
                            MessageType::Call | MessageType::Oneway | MessageType::InvalidMessageType => {
                                let err = anyhow::anyhow!("Unexpected message type {:?}", message_type);
                                Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ThriftError(err))
                            }
                        };
                        p.read_message_end()?;
                        result
                    }(de)
                }))
                .boxed()
        }
        {{/function:any_streams?}}{{/service:functions}}
    }

    impl<'a, T> {{service:name}} for T
    where
        T: AsRef<dyn {{service:name}} + 'a>,
        {{#service:extendedServices}}
        {{#extendedService:service}}
        T: {{extendedService:packagePrefix}}::client::{{service:name}},
        {{/extendedService:service}}
        {{/service:extendedServices}}
        T: Send,
    {
        {{#service:functions}}{{^function:any_streams?}}
        fn {{function:name}}(
            &self,
            {{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},
            {{/function:args}}
        ) -> std::pin::Pin<Box<dyn std::future::Future<Output = std::result::Result<{{!
            }}{{#function:returnType}}{{>lib/type}}{{/function:returnType}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + Send + 'static>> {
            self.as_ref().{{function:name}}(
                {{#function:args}}
                arg_{{field:name}},
                {{/function:args}}
            )
        }
        {{/function:any_streams?}}{{/service:functions}}
    }

    pub struct make_{{service:name}};

    /// To be called by user directly setting up a client. Avoids
    /// needing ClientFactory trait in scope, avoids unidiomatic
    /// make_Trait name.
    ///
    /// ```
    /// use bgs::client::BuckGraphService;
    ///
    /// let protocol = BinaryProtocol::new();
    /// let transport = HttpClient::new();
    /// let client = BuckGraphService::new(protocol, transport);
    /// ```
    impl dyn {{service:name}} {
        {{#service:annotations}}
        {{#annotation:value?}}
        pub const {{annotation:rust_name}}: &'static str = {{annotation:rust_value}};
        {{/annotation:value?}}
        {{/service:annotations}}
        pub fn new<P, T>(
            protocol: P,
            transport: T,
        ) -> Arc<impl {{service:name}} + Send + 'static>
        where
            P: Protocol<Frame = T>,
            T: Transport,
        {
            let _ = protocol;
            Arc::new({{service:name}}Impl::<P, T>::new(transport))
        }
    }

    /// The same thing, but to be called from generic contexts where we are
    /// working with a type parameter `C: ClientFactory` to produce clients.
    impl ClientFactory for make_{{service:name}} {
        type Api = dyn {{service:name}} + Send + Sync + 'static;

        fn new<P, T>(protocol: P, transport: T) -> Arc<Self::Api>
        where
            P: Protocol<Frame = T>,
            T: Transport + Sync,
        {
            {{service:name}}::new(protocol, transport)
        }
    }{{!
}}{{/program:services}}
{{!newline}}
